using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using UnityEngine;

public class NetConnTCP
{
    private string ip;
    private Int32 port = 12345;

    private bool isStarted = false;
    private bool isConnected = false;

    private TcpClient client = null;
    private System.Net.Sockets.NetworkStream stream = null;
    private Task connectTask = null;
    private Task<int> readTask = null;

    public static readonly int ReadBufferSize = 1024 * 1024;
    public static readonly int SendBufferSize = 1024 * 1024;

    private byte[] readBuffer = new byte[ReadBufferSize];

    public interface IReader
    {
        bool OnRead(byte[] buffer,int size,ref int retOffset,ref int retSize);
        void Reset();
    }

    public IReader reader;

    public delegate void OnConnected();
    public delegate void OnConnectError();
    public delegate void OnDisconnected();

    public event OnConnected onConnected;
    public event OnConnectError onConnectError;
    public event OnDisconnected onDisconnected;

    public NetConnTCP()
    {
        
    }

    public void SetAddress(string ip,Int32 port)
    {
        this.ip = ip;
        this.port = port;
    }

    public bool Start()
    {
        bool ok = false;

        do
        {
            if(isStarted)
            {
                break;
            }

            try
            {
                client = new TcpClient();

                connectTask = client.ConnectAsync(ip,port);

                isStarted = true;

                ok = true;
            }
            catch(Exception ex)
            {
                Debug.LogError("NetConnTCP Start Exception:" + ex);
            }

        }while(false);

        if(!ok)
        {
            _Stop();
        }

        return ok;
    }

    private void _Stop()
    {
        try
        {
            if(stream != null)
            {
                stream.Close();
            }
        }
        catch(Exception ex)
        {
            Debug.LogError("NetConnTCP _Stop stream Exception:" + ex);
        }

        try
        {
            if(client != null)
            {
                client.Close();
            }
        }
        catch(Exception ex)
        {
            Debug.LogError("NetConnTCP _Stop client Exception:" + ex);
        }               

        bool eventDisconnected = isConnected;

        isStarted = false;
        connectTask = null;
        readTask = null;
        stream = null;
        client = null;
        isConnected = false;

        if(reader != null)
        {
            reader.Reset();
        }

        if(eventDisconnected && onDisconnected != null)
        {
            onDisconnected();
        }
    }

    private void PostRead(int size)
    {
        readTask = null;

        int retOffset = 0;
        int retSize = readBuffer.Length;

        if(reader != null)
        {
            if(!reader.OnRead(readBuffer,size,ref retOffset,ref retSize))
            {
                _Stop();
                
                Debug.LogError("NetConnTCP PostRead package error");
                return;
            }
            
            if(!isStarted)
            {
                return;
            }

            if(retOffset + retSize > readBuffer.Length)
            {
                _Stop();

                Debug.LogError("check retOffset or retSize failed! retOffset:" + retOffset + " retSize:" + retSize);

                return;
            }
        }

        readTask = stream.ReadAsync(readBuffer,retOffset,retSize);
    }

    public void Update()
    {
        if(!isStarted)
        {
            return;
        }

        if(connectTask != null && connectTask.IsCompleted)
        {
            Task task = connectTask;
            connectTask = null;

            if(task.Status != TaskStatus.RanToCompletion)
            {
                Debug.Log("NetConnTCP ConnectError! task status:" 
                    + task.Status + 
                    (task.Exception != null ? 
                        (" Exception:" + task.Exception.ToString()) : "")
                );

                _Stop();

                // Debug.Log("call onConnectError.");

                if(onConnectError != null)
                {
                    onConnectError();
                }
                return;
            }

            stream = client.GetStream();

            isConnected = true;

            PostRead(0);

            if (onConnected != null)
            {
                onConnected();
            }

            return;
        }

        if(readTask != null && readTask.IsCompleted)
        {
            Task<int> task = readTask;
            readTask = null;

            if(task.Status != TaskStatus.RanToCompletion)
            {
                Debug.Log("NetConnTCP ReadError! task status:" 
                    + task.Status + 
                    (task.Exception != null ? 
                        (" Exception:" + task.Exception.ToString()) : "")
                );

                _Stop();
                return;
            }

            PostRead(task.Result);
        }
    }

    public void Stop()
    {
        if(!isStarted)
        {
            return;
        }

        _Stop();
    }

    public bool SendMessage(byte[] buffer, int offset, int count)
    {
        bool ok = false;

        do
        {
            if(!isStarted)
            {
                break;
            }

            if(null == stream)
            {
                break;
            }

            try
            {
                stream.Write(buffer,offset,count);

                ok = true;

                Debug.Log("NetConnTCP SendMessage count:" + count);
            }
            catch(Exception ex)
            {
                Debug.LogError("NetConnTCP SendMessage Exception:" + ex + " msg count:" + count);
            }

        }while(false);

        return ok;
    }
}